Skip to content

_MeshIndexSet first pass - #7149

Merged
stephenworsley merged 23 commits into
SciTools:FEATURE_index_setfrom
trexfeathers:mesh-index-set
Jul 15, 2026
Merged

_MeshIndexSet first pass#7149
stephenworsley merged 23 commits into
SciTools:FEATURE_index_setfrom
trexfeathers:mesh-index-set

Conversation

@trexfeathers

Copy link
Copy Markdown
Contributor

No description provided.

@trexfeathers trexfeathers added Feature: UGRID Type: Feature Branch Highlight this for a feature branch labels Jun 10, 2026
@trexfeathers

Copy link
Copy Markdown
Contributor Author

pre-commit.ci autofix

@trexfeathers trexfeathers mentioned this pull request Jun 10, 2026
4 tasks
@trexfeathers

Copy link
Copy Markdown
Contributor Author

@stephenworsley please can you review the approach I have used with this code, accepting that various TODOs are deferred to #7151. Thanks!

@trexfeathers trexfeathers linked an issue Jun 10, 2026 that may be closed by this pull request
4 tasks
@trexfeathers
trexfeathers marked this pull request as ready for review June 10, 2026 15:02
Comment thread lib/iris/mesh/components.py Outdated
raise NotImplementedError()

def is_view_of(self, other: MeshXY) -> bool:
"""Whether this instance is either itself, or a view of the given :class:`MeshXY`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered how this ought to behave when you've sliced or indexed a _MeshIndexSet? If I'm interpreting the code correctly, it looks like you ought to end up with another smaller _MeshIndexSet. In such a case, I could imagine you might want this method to also return True.

I also wonder if it might be worth considering two different methods, one for comparing a _MeshIndexSet to a MeshXY, another for comparing two _MeshIndexSets. I haven't entirely thought through which contexts you might expect to call these methods so I've not come to a conclusion on this myself yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not intended to be possible, and I've taken some steps to avoid it happening:

case _MeshIndexSet():
# Should not base an index set on another index set - base
# on the original mesh instead.
# TODO: do we need to double-check that self.location
# matches mesh_index_set.location? Any other matching to
# check too?
mesh_index_set = mesh
kwargs = dict(
mesh=mesh_index_set.mesh,
location=mesh_index_set.location,
indices=mesh_index_set.indices[keys],
)

Comment thread lib/iris/mesh/components.py Outdated
result = [self.to_MeshCoord(location=location, axis=ax) for ax in self.AXES]
return tuple(result)

def is_view_of(self, other: "MeshXY") -> bool:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Offline conversation with @stephenworsley: is_view_of is not necessary, and any value it might offer is offset by potential confusion. We should remove it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread lib/iris/mesh/components.py Outdated
result_dict = {k: v for k, v in self._members.items() if id(v) in result_ids}
return result_dict

def index(

@trexfeathers trexfeathers Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Offline conversation with @stephenworsley: this concept of mutability only really extends to altering the membership of the Manager, there is no decent way 1 to protect the coordinates/connectivities themselves from modification, since users are free to assign those to their own variables, and/or to modify the arrays within them.

If we're comfortable with this 'insecurity', then we could consider generating new constituent coordinates/connectivities that explicitly share the same NumPy array as the ones on the original Mesh (as opposed to directly using the slicing API, since that explicitly creates a copy). That would allow the MeshIndexSet to be a true view, with live updating, potentially avoiding the weirdness of re-indexing every time a Manager is requested.

Footnotes

  1. I need to read up on Ensure MeshCoord points and bounds always agree with the Mesh (while keeping Mesh mutable) #4757

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't turn out as expected - indexing arrays using another array of integers NEVER returns a view - but I've got something working.

a6542ee

Comment thread lib/iris/mesh/components.py Outdated
@trexfeathers

trexfeathers commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Note that currently printing MeshCoords based on _MeshIndexSet is very slow. Computing points and bounds seems OK, which is the bit I was originally worried about.

EDIT: fixed by ab688cf

def cube_dims(self, cube):
raise NotImplementedError()

def as_mesh(self) -> MeshXY:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth considering if we want to realise the data when we call this. This ought to be the most "robust" way of handling this operation. There's maybe a case for keeping this lazy, in order to minimise memory use, but I'm not convinced this is the correct behaviour or even something we can properly implement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenworsley stephenworsley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recorded some stuff to follow up later, but this all looks good in principle. A good start for the feature branch, good work!

Comment thread lib/iris/mesh/components.py Outdated
if connectivity.location_axis == 1:
new_values = new_values.T
if connectivity.start_index == 1:
new_values = new_values + 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this isn't just new_values += connectivity.start_index?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread lib/iris/mesh/components.py Outdated

# Map node indices in "values" to their new zero-based positions
# in "node_indices".
order = node_indices.argsort()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this code is focused on ***_node_connectivity. I suspect this might fail with something like face_edge_connectivity. It may be worth just throwing an error in such cases for the time being. With that said, I don't think this is a blocker to moving forward with work on this feature branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, node-based operations are special. Edge- and face-based operations are much easier to calculate; that's why the code looks the way it does?

Comment thread lib/iris/mesh/components.py Outdated
if connectivity is not None:
indexing = indices_dict[connectivity.location]
if indexing is not None:
new_values = connectivity.indices_by_location(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need addressing right now, but I think this bit of code could definitely benefit from thinking about making these variable names clearer. In particular, I think this could benefit from being something other than new_values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread lib/iris/mesh/components.py Outdated
order = node_indices.argsort()
old_sorted = da.from_array(node_indices[order])
new_ids_sorted = da.arange(len(node_indices))[order]
positions = functools.partial(da.searchsorted, old_sorted)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new_ids_sorted and positions should be the same for every loop so we should probably be calculating these outside the for loop. Maybe worth adding a bit of logic to ensure they aren't called unnecessarily.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array=new_ids_sorted,
indexing=new_values,
pre_index=positions,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I finally got my head around what this bit of logic does. While there's bits I think can be tidied up, I'm happy with what this does in principle.

@stephenworsley stephenworsley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look good also! This can get merged now.

@stephenworsley
stephenworsley merged commit 5d1180d into SciTools:FEATURE_index_set Jul 15, 2026
15 of 19 checks passed
trexfeathers added a commit that referenced this pull request Jul 29, 2026
* `_MeshIndexSet` first pass (#7149)

* Use Monotonicity error in Cube indexing, plus test coverage.

* Implement MeshIndexSetMetadata.

* Changes to cube.mesh type hinting.

* Create experimental/mesh_coord_indexing.py .

* Implement MeshIndexSet.

* Render _MeshIndexSet in docs.

* Easy CI fixes.

* style: pre-commit fixes

* Add TODO for later type hinting.

* Missing sphinx-needs item.

* Remove linkable UGRID footnotes to avoid duplication warning.

* Fix doctest.

* TODO comments.

* Fix doctest.

* TODO comment.

* Fix doctest.

* Achieve an accurate view of Mesh via Dask arrays and timestamps.

* Remove Mesh.is_view_of.

* Fixes after rough testing.

* Prevent overzealous updates.

* Disconnect new meshes from original meshes.

* Review suggestions.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

* Refactor `_MeshIndexSet` for true lazy support (#7209)

* Refactor _MeshIndexSet for true lazy support.

* Update lib/iris/mesh/components.py

* Resolve MyPy failures.

* Review comments.

* Better naming.

* Correct mathematical terminology.

* (Partially) Finalise meshindexset (#7207)

* remove unused import

* made _MeshXYMixin get and set state abstract

* bonus comments

* Add _MeshCoordinateManager as parent ot 1d and 2d

* typing with _MeshConnectivityManagerBase

* fix typing generator

* accounting for dask array in _MeshConnectivityManagerBase indexed (to be discussed)

* hacky way to fix test

* Sequence check for MeshCoord

* _MeshCoordinateManagerBase saves and gets _view_message with state

* Raise exception when MeshXY.from_coords is called with MeshCoords

* validation to _MeshIndexSet __init__

* Typing _MeshIndexSet. Other areas touched for mypy

* Raise exception when saving _MeshIndexSet. Could possibly be done earlier

* Added _MeshIndexSet._NOT_IMPLEMENTED

* using match-case for _MeshIndexSet._calculate_node_indices and removing the null case because it is handled in the __init__

* Prevent mismatch of location in MeshCoord.__init__ when supplied a _MeshIndexSet

* Further typing of _MeshXYMixin

* post merge numpy fixes

* Added forgotten bonus comment

* fixed logic mistake in summary

* updating remove_duplicate_nodes in tests to deal with `None`s

* Update _MeshIndexSet init validation to use isinstance

* remove finished todo

* remove completed todos

* remove unused import

* update _calculate_node_bool_index to use match case

* fixed ambiguous truthy and reformatted indexed

* updated _MeshIndexSet saving is not yet supported error message

* Remove completed todos

* fixed from_coords type check and updated error message

* improverd _MeshIndexSet.__init__ error messages

* removed completed todo

* Updated MeshCoord.__getitem__ error message

* refactor _MeshCoordinateManagerBase.indexed

* Cope with scalar indexing, plus view_message fix.

* Updated expected exception messages

* Made _MeshIndexSet.__init__ indices more lenient

---------

Co-authored-by: Martin Yeo <martin.yeo@metoffice.gov.uk>

* Docstrings for MeshIndexSet work (#7213)

* Docstrings for MeshIndexSet work.

* More explication.

* Indicate default `start_index`.

Co-authored-by: pt331 <144435193+pt331@users.noreply.github.com>

---------

Co-authored-by: pt331 <144435193+pt331@users.noreply.github.com>

* Fixes based on early testing. (#7216)

* Tests for indexing meshes (#7218)

* Barebones tests

* assertion fix and allowing for single value index

* fixed tests/stock/mesh topology_dimension

* Update as_mesh to deal with locations not existing

* Copied as_mesh changes from unit tests branch

* Applied equality changes offered by @trexfeathers

* Remove cube_mesh_node from test_subset_indexing_new_mesh test

* updating to full tests - test_subset_indexing_mesh_index_set fails now

* Fixed looking at the wrong part of cube and _MeshIndexSet test

* Unit tests for MeshIndexSet. (#7219)

* Unit tests for MeshIndexSet.

* Convert existing tests to use MonotonicityError.

* Unit tests for mesh_coord_indexing.

* Wider topology and location coverage.

* Tests for printing.

* Indexing improvements.

* Test lazy cases.

* Increased test coverage.

* Increased coverage.

* Increased coverage.

* Fix test typo.

* Mark _MeshIndexSetMetadata as private.

* eq unit tests.

* Make _MeshIndexSet hashable as intended.

* More thorough varied mesh testing.

* MeshIndexSet What's News (#7225)

* What's New entries for MeshIndexSet.

* Extra experimental note.

* Review comments.

* Fix pre-commit failures.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pt331 <144435193+pt331@users.noreply.github.com>
@scitools-ci scitools-ci Bot removed this from 🚴 Peloton Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature: UGRID Type: Feature Branch Highlight this for a feature branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create MeshIndexSet class

2 participants